/*
pthreadmulti.c: 
1. init papi
2. assign papi to monitor more events than the number of counters
3. launch five ptheads, each pthread call do_stuff(#) with different #
4. print out counter values

./pthreadmulti 
do_stuff(0)-- 6954 1177 178 42 52 80 22 48 15 2502
do_stuff(3)-- 1034086 900538 100081 33 32 60 41 56 1 627001
do_stuff(4)-- 16248 20018 1300 78 15 53 13 19 0 3857
do_stuff(1)-- 22744221 43903346 6800466 299 281 773 357 573 55 3870893
do_stuff(2)-- 28818349 63148594 8582179 594 427 918 379 609 40 2765957

*/


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <papi.h>


#define ERROR_RETURN(retval) { fprintf(stderr, "Error %d %s:line %d: \n", retval,__FILE__,__LINE__);  exit(retval); }
#define NUM_EVENT 2
#define MAX_TEST_EVENTS 10
#define N 5

//PAPI preset Hardware events, use the papi_avail (under ./papi-5.1.0/src/utils) to find out
unsigned mytest_events[10] = {
PAPI_TOT_CYC,
PAPI_TOT_INS,
PAPI_BR_CN,
PAPI_BR_MSP,
PAPI_L1_DCM,
PAPI_L1_ICM,
PAPI_L2_ICM,
PAPI_L2_TCM,
PAPI_L3_TCM,
PAPI_STL_ICY,
};

static pthread_mutex_t pmutex;

//micro benchmarks
int fibonachi(int n){
	if(n<3)
		return 1;
	else 
		return fibonachi(n-1)+fibonachi(n-2);
}

double cal_sqrt(int n){
	double a = 1.3;
	int i;
	for(i=0; i<n; i++)
		a = sqrt(a);

	return a;
}

int do_write(int n){
	FILE* fptr=NULL;
	int i;
	if((fptr=fopen("/dev/null","w"))==NULL){
		fprintf(stderr, "Cannot open /dev/null to write\n");
		exit(-1);
	}

	for(i=0; i<n; i++)
		fprintf(fptr,"%d",i);
	fclose(fptr);

	return 0;
}

int do_read(int n){
	FILE* fptr=NULL;
	int i;
	int a;
	if((fptr=fopen("/dev/zero","r"))==NULL){
		fprintf(stderr, "Cannot open /dev/zero to read\n");
		exit(-1);
	}

	for(i=0; i<n; i++)
		fscanf(fptr,"%d",&a);
	fclose(fptr);

	return 0;
}

double do_flops(int n)
{
	int i;
	double a = 0.5, b = 6.2, c;

	for (i=0; i < n; i++)
		c += a * b;
	return c;
}

void *do_stuff(void *arg){

	int sel = *((int *)arg);
	int i;
	int EventSet = PAPI_NULL;
	long long values[MAX_TEST_EVENTS];
	int retval;

//register current thread to PAPI
	if ( (retval=PAPI_register_thread()) != PAPI_OK)
                        ERROR_RETURN(retval);

//create a generic PAPI event
	if ( (retval=PAPI_create_eventset(&EventSet)) != PAPI_OK)
                   ERROR_RETURN(retval);

//set PAPI to monitor component 0, 0 means CPU
	if ( (retval=PAPI_assign_eventset_component( EventSet, 0 )) != PAPI_OK)
               		ERROR_RETURN(retval);

//convert the generic event to multiplexing event
	if ( (retval=PAPI_set_multiplex( EventSet )) != PAPI_OK)
               		ERROR_RETURN(retval);

//add events to monitor, note we assign more events than the number of physical counters
	for(i=0; i<MAX_TEST_EVENTS; i++){
		if ( (retval=PAPI_add_event( EventSet, mytest_events[i] )) != PAPI_OK)
               		ERROR_RETURN(retval);
	}

//start counting
	if ( (retval=PAPI_start(EventSet)) != PAPI_OK)
                   ERROR_RETURN(retval);

//do something
	if(sel==1){
		do_read(1e5);
	}
	else if(sel==2){
		do_write(1e5);
	}
	else if (sel==3){
		do_flops(1e5);
	}
	else if (sel==4){
		fibonachi(15);
	}

//stop/read counters
	if ( (retval=PAPI_stop(EventSet,values)) != PAPI_OK)
                        ERROR_RETURN(retval);
	
//release event related resources
	if ( (retval=PAPI_cleanup_eventset(EventSet)) != PAPI_OK)
                       ERROR_RETURN(retval);

	if ( (retval=PAPI_destroy_eventset(&EventSet)) != PAPI_OK)
                       ERROR_RETURN(retval);

	EventSet = PAPI_NULL;

//unregister current pthread
	if ( (retval=PAPI_unregister_thread()) != PAPI_OK)
                        ERROR_RETURN(retval);

//print out results, in order to avoid break in the middle, use mutex to protect printout
	pthread_mutex_lock(&pmutex);
	fprintf(stdout, "do_stuff(%d)--", sel);
	for(i=0; i<MAX_TEST_EVENTS; i++){
		fprintf(stdout, " %lld", values[i]);
	}
	fprintf(stdout, "\n");
	pthread_mutex_unlock(&pmutex);

//free resource
	pthread_exit(0);
}


int main(int argc, char *argv[]) {
	int a[N];
	int i=0;
	int retval;
	pthread_t pth[N];
	int *ptr[N];

//init PAPI
	if((retval = PAPI_library_init(PAPI_VER_CURRENT)) != PAPI_VER_CURRENT )
		ERROR_RETURN(retval);

//init pthread support
	if((retval = PAPI_thread_init( ( unsigned long ( * )( void ) ) ( pthread_self ) ))!= PAPI_OK)
		ERROR_RETURN(retval);
	if(pthread_mutex_init(&pmutex, NULL)!=0){
				fprintf(stderr, "mutex init failed\n");
				exit(-1);
			}
//init multiplexing support
	if ((retval=PAPI_multiplex_init()) != PAPI_OK)
                		ERROR_RETURN(retval);

//launch threads
		for (i=0; i<N; i++){
			a[i] = i;
			pthread_create(&pth[i],NULL,do_stuff,(void *)(&a[i]));

		}
		for (i=0; i<N; i++){
			pthread_join(pth[i], (void **)&(ptr[i]));
		}		

//free resourec
	pthread_mutex_destroy(&pmutex);
	PAPI_shutdown();
	return 0;
}

